package com.atguigu.gulimall.auth.web;

import com.alibaba.fastjson.TypeReference;
import com.atguigu.common.constant.SessionAttrKeyConstant;
import com.atguigu.common.constant.SmsContant;
import com.atguigu.common.exception.BizCodeEnum;
import com.atguigu.common.utils.R;
import com.atguigu.gulimall.auth.feign.MemberFeignService;
import com.atguigu.gulimall.auth.feign.SmsSCFeignService;
import com.atguigu.common.vo.MemberEntity;
import com.atguigu.gulimall.auth.vo.RegisterVo;
import com.atguigu.gulimall.auth.vo.UserLoginVo;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;

import javax.servlet.http.HttpSession;
import javax.validation.Valid;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@Controller
public class LoginController {

    @Autowired
    private SmsSCFeignService smsSCFeignService;

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Autowired
    private MemberFeignService memberFeignService;

    @GetMapping("/login.html")
    public String loginPage(HttpSession session){
        Object attribute = session.getAttribute(SessionAttrKeyConstant.LOGIN_USER);
        String attribute1 = (String)session.getAttribute("msg");
        System.out.println("attribute1====>" + attribute1);
        if(attribute!=null){
            return "redirect:http://gulimall.com";
        }else{
            return "login";
        }
    }

    @PostMapping("/login")
    public String login(UserLoginVo userLoginVo, RedirectAttributes redirectAttributes, HttpSession session){
        // 远程服务调用
        R r = memberFeignService.login(userLoginVo);
        if(r.getCode() == 0){
            MemberEntity memberRespVo = r.getData("data",new TypeReference<MemberEntity>() {});
            System.out.println("memberRespVo="+memberRespVo);
            session.setAttribute(SessionAttrKeyConstant.LOGIN_USER,memberRespVo);
            // 成功
            return "redirect:http://gulimall.com";
        }else{
            HashMap<String,String> errors = new HashMap<>();
            errors.put("msg",r.getData("msg",new TypeReference<String>(){}));
            redirectAttributes.addFlashAttribute("errors",errors);
            return "redirect:http://auth.gulimall.com/login.html";
        }
    }

    @ResponseBody
    @GetMapping("/sms/sendcode")
    public R sendCode(@RequestParam("phone") String phone){

        // TODO 接口防刷

        // 处理一个手机号码60s内多次获取短信验证码问题
        // 解决思路：将短信验证码存储在redis中，key为phoneNum，value为验证码和存储时系统的当前时间。之间用下划线分隔
        // 从redis中查询为null则调用发送短信验证码，若查询不为空则判断是否超过60s，
        // 是则再次调用发送短信验证码，否则返回提示信息。
        String s = stringRedisTemplate.opsForValue().get(SmsContant.SMS_CODE_CACHE_PREFIX + phone);

        if (StringUtils.isNotEmpty(s)) {
            // 获取存储时的系统时间
            long saveTime = Long.parseLong(s.split("_")[1]);
            // 单位为毫秒,因此60秒要转化为毫秒即60000
            if(System.currentTimeMillis() - saveTime < 6000){
                return R.error(BizCodeEnum.SMS_CODE_EXCEPTION.getCode(),BizCodeEnum.SMS_CODE_EXCEPTION.getMsg());
            }
        }

        // 第三方应用只支持数字所以这个不行
        // String code = UUID.randomUUID().toString().substring(0, 5);
        Random random = new Random();
        // 生成一个四位数,范围从1000到9999
        /**
         * random.nextInt(9000) 是 java.util.Random 类中的 nextInt(int bound) 方法的一个应用实例。
         * 这个方法的作用是从 [0, bound) 这个半开区间内（包括0但不包括bound）随机生成一个整数。
         * 当你调用 random.nextInt(9000) 时，它会生成一个0到8999之间（含0而不含9000）的随机整数。
         * 由于我们希望生成的是四位数，且最小值应该是1000，所以在生成的结果上加了1000，
         * 即 random.nextInt(9000) + 1000，这样最终得到的就是一个从1000到9999之间的随机四位数。
         * */
        int code = random.nextInt(9000) + 1000;
        System.out.println("发送短信方法执行到这里了,验证码是" + code);
        stringRedisTemplate.opsForValue().set(SmsContant.SMS_CODE_CACHE_PREFIX+phone,code+"_"+System.currentTimeMillis(),100, TimeUnit.DAYS);
        smsSCFeignService.sendCode(phone,code+"");
        return R.ok();
    }

    /**
    @RequestMapping("/reg.html")
    public String regPage(){
        System.out.println("执行到这里了reg.html");
        return "reg";
    }
    */


    @PostMapping("/register")
    public String register(@Valid RegisterVo registerVo, BindingResult bindingResult, Model model, RedirectAttributes redirectAttributes){
    // public String register(@Valid RegisterVo registerVo, BindingResult bindingResult, Model model){
        // 校验出错,转发到注册页
        if (bindingResult.hasErrors()) {
            Map<String, String> errors = bindingResult.getFieldErrors().stream().collect(Collectors.toMap(item -> {
                return item.getField();
            }, item -> {
                return item.getDefaultMessage();
            }));
            model.addAttribute("errors",errors);
            // return "forward:/reg.html"; 出现问题： Request method 'POST' not supported
            // 用户注册 -> /regist[POST] ---> 转发/reg.html (路径映射默认都是get方式访问的)[registry.addViewController("/reg.html").setViewName("reg");]
            // 出现问题的原因：表单的提交使用的是post请求，会原封不动的转发给reg.html，但是/reg.html（路径映射默认都是get方式访问）
            // 用POST请求

            // return "reg"; //又有问题 : 刷新页面，会重复提交表单

            /**
             * 在Web开发中，尤其是Java Servlet或Spring MVC等框架中，请求转发（Forward）和重定向（Redirect）是两种常见的HTTP请求处理机制。它们对表单提交行为的影响主要体现在以下方面：
             *
             * 请求转发（Forward）：
             *
             * 请求转发发生在服务器端，客户端发出一个请求后，服务器接收到请求并决定转发到另一个资源（如另一个Servlet或JSP页面）继续处理，整个过程对于客户端而言是透明的，客户端只知道最初的URL。
             * 当表单提交后，服务器通过请求转发到下一个页面。在这个过程中，浏览器的地址栏并不会改变，仍然是原始表单提交的URL。
             * 如果用户在此时刷新页面（按F5键或者点击刷新按钮），浏览器会基于当前地址再次发送相同的POST请求，这通常会导致表单数据的重复提交。
             * 重定向（Redirect）：
             *
             * 重定向则是服务器端完成处理后，向客户端发送一个特殊的响应状态码（通常是302 Found或303 See Other），同时提供一个新的URL让客户端去访问。
             * 客户端接收到重定向指令后，会停止当前请求，并发起一个新的GET请求到指定的新URL。
             * 当表单提交后，服务器通过重定向将用户引导至另一个页面。这时，浏览器的地址栏会显示新的URL。
             * 用户刷新页面时，浏览器会基于新的URL发起请求，但由于这是一个新的GET请求，而非之前的POST请求，因此不会携带之前提交的表单数据，从而避免了重复提交表单。
             * 总结起来，请求转发时刷新页面相当于重复执行了原来的POST操作，而重定向实际上是改变了用户的访问位置，新请求不再是提交操作，所以能够有效防止表单的重复提交。
             */

            // 转发，数据都封装在Model中，而重定向获取不到 -> 解决方案：使用 RedirectAttributes
            redirectAttributes.addFlashAttribute("errors",errors);

            // 解决方案:使用重定向
            // return "redirect:/reg.html"; 会出现问题：重定向到服务端口地址  -> url:192.168.103.24:20000/reg.html

            // 解决方案:写完整的域名路径
            return "redirect:http://auth.gulimall.com/reg.html";

            /**
                TODO 重定向携带数据,利用session原理.将数据放在session中。只要跳到下一个页面取出这个数据以后,session里面的数据就会删掉
                TODO 出现问题：分布式下重定向使用session存储数据会出现一些问题
                TODO 解决方案：后续会说明
             */
        }

        // 验证码校验
        String code = registerVo.getCode();
        String s = stringRedisTemplate.opsForValue().get(SmsContant.SMS_CODE_CACHE_PREFIX + registerVo.getPhone());

        if (StringUtils.isNotEmpty(s)) {
            if (code.equals(s.split("_")[0])){ // 存储时加入了当前系统时间,因此,需要先分割
                // 删除验证码 , 令牌机制
                // stringRedisTemplate.delete(SmsContant.SMS_CODE_CACHE_PREFIX + registerVo.getPhone());
                // 远程服务调用 , 将数据存入数据库 , 进行真正的注册
                R r = memberFeignService.regist(registerVo);
                if(r.getCode() == 0){
                    // 成功
                    return "redirect:http://auth.gulimall.com/login.html";
                }else{
                    HashMap<String, String> errors = new HashMap<>();
                    /**
                     * public static R error(int code, String msg) {
                     * 		R r = new R();
                     * 		r.put("code", code);
                     * 		r.put("msg", msg);
                     * 		return r;
                     * }
                     * */
                    errors.put("msg",r.getData("msg",new TypeReference<String>(){}));
                    redirectAttributes.addFlashAttribute("errors",errors);
                    return "redirect:http://auth.gulimall.com/reg.html";

                }
            } else {
                // 验证码输入错误
                HashMap<String, String> errors = new HashMap<>();
                errors.put("code","验证码错误");
                redirectAttributes.addFlashAttribute("errors",errors);
                return "redirect:http://auth.gulimall.com/reg.html";
            }
        } else {
            // 验证码过期 (这种就是页面60s之后才输入验证码的情况) || 获取验证码之后用户输入了一个新的手机号没有注册过的手机号
            Map<String, String> errMap = new HashMap<>();
            errMap.put("code", "验证码错误");
            // 封装异常返回前端显示
            redirectAttributes.addFlashAttribute("errors", errMap);// flash，session中的数据只使用一次
            return "redirect:http://auth.gulimall.com/reg.html";// 采用重定向有一定防刷功能
        }

    }
}
